____ _ _ _ _
| _ \ ___ | |_ (_) _ __ ___ __| | (_) __ _
| |_) | / _ \ | __| | | | '_ \ / _ \ / _| | | | / _ |
| _ < | __/ | |_ | | | |_) | | __/ | (_| | | | | (_| |
|_| \_\ \___| \__| |_| | .__/ \___| \__,_| |_| \__,_|
|_|
- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b- `b
ÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻÂŻ
Hashtabelle
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
top
In der Informatik bezeichnet man eine spezielle Indexstruktur als Hashtabelle (englisch hash table oder hash map) bzw. Streuwerttabelle. Sie wird verwendet, um Datenelemente in einer groĂen Datenmenge zu suchen bzw. aufzufinden (Hash- oder Streuspeicherverfahren).
GegenĂŒber alternativen Index-Datenstrukturen wie Baumstrukturen (z. B. ein B+-Baum) oder Skip-Listen zeichnen sich Hashtabellen ĂŒblicherweise durch einen konstanten Zeitaufwand bei EinfĂŒge- bzw. Entfernen-Operationen aus.
Contents
âą Hashverfahren
âą Der Algorithmus
âą Kollisionen
âą Vorteile
âą Nachteile
âą Varianten
âą Kuckucks-Hashing
âą Algorithmen
âą Anwendung
âą Symboltabellen
âą Datenbanken
âą Siehe auch
âą Literatur
âą Weblinks
âą Einzelnachweise
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Hashverfahren
Das Hashverfahren ist ein Algorithmus zum Suchen von Datenobjekten in groĂen Datenmengen. Es basiert auf der Idee, dass eine mathematische Funktion, die Hashfunktion, die Position eines Objektes in einer Tabelle berechnet. Dadurch erĂŒbrigt sich das Durchsuchen vieler Datenobjekte, bis das Zielobjekt gefunden wurde.
Der Algorithmus
Beim Hashverfahren werden die Zieldaten in einer Hashtabelle gespeichert. Dabei dient nicht der SchlĂŒssel, der das Datenobjekt eindeutig identifiziert, als Index, sondern der Hashwert, der von einer Hashfunktion aus dem SchlĂŒssel berechnet wird. Der durch den Hashwert festgelegte Speicherort eines Datenobjektes in der Tabelle wird auch als Bucket bezeichnet (englisch BehĂ€lter).
Im Idealfall bekommt jedes Objekt einen eigenen Bucket (d. h. keine Kollisionen, s. u.).
In der Praxis wird die Tabelle als ein Array implementiert.
Kollisionen
Hashfunktionen sind im Allgemeinen nicht injektiv, d. h. zwei unterschiedliche SchlĂŒssel können zum selben Hashwert, also zum selben Feld in der Tabelle, fĂŒhren. Dieses Ereignis wird als Kollision bezeichnet. In diesem Fall muss die Hashtabelle mehrere Werte an derselben Stelle / in demselben Bucket aufnehmen.
Eine Kollision benötigt bei der Suche eine spezielle Behandlung durch das Verfahren: ZunĂ€chst wird aus einem SuchschlĂŒssel wieder ein Hashwert berechnet, der den Bucket des gesuchten Datenobjektes bestimmt; dann muss noch durch direkten Vergleich des SuchschlĂŒssels mit den Objekten im Bucket das gesuchte Ziel bestimmt werden.
Zur Behandlung von Kollisionen werden kollidierte Daten nach einer Ausweichstrategie in alternativen Feldern oder in einer Liste gespeichert. Schlimmstenfalls können Kollisionen zu einer Entartung der Hashtabelle fĂŒhren, wenn wenige Hashwerte sehr vielen Objekten zugewiesen wurden, wĂ€hrend andere Hashwerte unbenutzt bleiben.
Kollisionsauflösungsstrategien
Um das Kollisions-Problem zu handhaben, gibt es diverse Kollisionsauflösungsstrategien.
Geschlossenes Hashing mit offener Adressierung
Eine Möglichkeit wird geschlossenes Hashing mit offener Adressierung genannt. Wenn dabei ein Eintrag an einer schon belegten Stelle in der Tabelle abgelegt werden soll, wird stattdessen eine andere freie Stelle genommen. HÀufig werden drei Varianten unterschieden (vgl. #Algorithmen):
lineares Sondieren
es wird um ein konstantes Intervall verschoben nach einer freien Stelle gesucht. Meistens wird die IntervallgröĂe auf 1 festgelegt.
quadratisches Sondieren
Nach jedem erfolglosen Suchschritt wird das Intervall quadriert.
doppeltes Hashen
eine weitere Hash-Funktion liefert das Intervall.
Offenes Hashing mit geschlossener Adressierung
Eine weitere Möglichkeit ist offenes Hashing mit geschlossener Adressierung. Anstelle der gesuchten Daten enthÀlt die Hashtabelle hier BehÀlter (englisch Buckets), die alle Daten mit gleichem Hash-Wert aufnehmen. Bei einer Suche wird also zunÀchst der richtige ZielbehÀlter berechnet. Damit wird die Menge der möglichen Ziele erheblich eingeschrÀnkt.
Dennoch mĂŒssen abschlieĂend die verbliebenen Elemente im BehĂ€lter durchsucht werden. Im schlimmsten Fall kann es passieren, dass alle Elemente gleiche Hash-Werte haben und damit im selben Bucket abgelegt werden. In der Praxis kann das aber durch die Wahl einer geeigneten GröĂe fĂŒr die Hashtabelle sowie einer geeigneten Hash-Funktion vermieden werden.
Oft wird die Verkettung durch eine lineare Liste pro BehÀlter realisiert.
Vorteile
Je nach Anwendungsfall hat eine Hashtabelle Vorteile sowohl in der Zugriffszeit (gegenĂŒber anderen Baumindexstrukturen) als auch im benötigten Speicherplatz (gegenĂŒber gewöhnlichen Arrays).
Idealerweise sollte die Hashfunktion fĂŒr die zu speichernden Daten so gewĂ€hlt sein, dass die Anzahl der Kollisionen minimiert wird und unter einer Konstante bleibt; je nach Hashfunktion muss die Hashtabelle dafĂŒr ungenutzte Felder enthalten (in der Praxis ĂŒblicherweise 20 bis 30 Prozent). Trifft dies zu, dann benötigt eine Hashtabelle mit n {\displaystyle n} gespeicherten Elementen per Zugriff (englisch Look-Up) auf einen Hashtabellen-Eintrag im Mittel nur konstanten Zeitaufwand (O(1)).
Im Vergleich dazu ist der Zugriff auf ein Element in einem B-Baum in der GröĂenordnung von O ( log ⥠⥠n ) {\displaystyle O(\log n)} , wobei das n der Anzahl der in der Hashtabelle gespeicherten EintrĂ€ge entspricht. Die komplette Baum-Datenstruktur benötigt Speicher in der GröĂenordnung von O ( n ) {\displaystyle O(n)} .
Nachteile
Wegen der möglichen Kollisionen hat eine Hashtabelle im Worst-Case ein sehr schlechtes Zugriffszeit-Verhalten. Dieser wird mit O ( n ) {\displaystyle O(n)} abgeschÀtzt; es werden dabei also alle EintrÀge in der Tabelle durchsucht.
Als FĂŒllgrad wird die Anzahl der gespeicherten Elemente geteilt durch die Anzahl aller Buckets bezeichnet:
FĂŒllgrad = gespeicherte Elemente/Buckets.
Mit steigendem FĂŒllgrad wĂ€chst die Wahrscheinlichkeit einer Kollision, und die Entartung nimmt zu. Dann kann nur eine VergröĂerung der Tabelle mit nachfolgender Restrukturierung wieder zu akzeptablem Laufzeitverhalten fĂŒhren.
AuĂerdem bringt eine der Suchoperation nachfolgende Nachbarschaftssuche nichts, da eine Ordnungsbeziehung zwischen den SchlĂŒsseln erklĂ€rtermaĂen nicht gepflegt wird (s. a. Abschnitt Datenbanken).
Varianten
Es gibt mehrere Varianten des Hashverfahrens, die sich fĂŒr bestimmte Daten besser eignen. Ein wichtiger Faktor hierbei ist die Dynamik, mit der sich die Anzahl der Elemente Ă€ndert. Das offene Hashing löst dieses Problem, nimmt aber EinbuĂen bei den Zugriffszeiten in Kauf. Das geschlossene Hashing ist hingegen auf explizite Strategien zur Kollisionsbehandlung angewiesen.
Vorsicht: Die Bezeichnungen offenes bzw. geschlossenes Hashing werden auch in genau umgekehrter Bedeutung verwendet.
Hashing mit Verkettung
Beim Hashing mit Verkettung (englisch separate chaining) ist die Hash-Tabelle so strukturiert, dass jeder BehĂ€lter eine dynamische Datenstruktur aufnehmen kann â beispielsweise eine Liste oder einen Baum. Jeder SchlĂŒssel wird dann in dieser Datenstruktur eingetragen oder gesucht. So ist es problemlos möglich, mehrere SchlĂŒssel in einem BehĂ€lter abzulegen, was allerdings zu mehr oder weniger verlĂ€ngerten Zugriffszeiten fĂŒhrt. Die Effizienz des Zugriffs wird dabei davon bestimmt, wie schnell DatensĂ€tze in die gewĂ€hlte Datenstruktur eingefĂŒgt und darin wiedergefunden werden können. Hashing mit Verkettung ist bei Datenbanken eine sehr gĂ€ngige Indizierungsvariante, wobei sehr groĂe Datenmengen mittels Hashtabellen indiziert werden. Die GröĂe der Buckets ist in Datenbanksystemen ein Vielfaches der SektorengröĂe des Speichermediums. Der Grund dafĂŒr ist, dass die Datenmenge nicht mehr im Hauptspeicher gehalten werden kann. Bei einer Suchanfrage muss das Datenbanksystem die Buckets sektorenweise einlesen.
Jeder BehĂ€lter ist unabhĂ€ngig und weist eine Art Liste von EintrĂ€gen mit demselben Index auf. Die Zeit fĂŒr Operationen der Hashtabelle ist die Zeit zum Suchen des BehĂ€lters, die konstant ist, plus die Zeit fĂŒr die Listenoperation. In einer guten Hashtabelle hat jeder BehĂ€lter keinen oder einen Eintrag und manchmal zwei oder drei, aber selten mehr. Daher werden fĂŒr diese FĂ€lle zeit- und raumeffiziente Strukturen bevorzugt. Strukturen, die fĂŒr eine ziemlich groĂe Anzahl von EintrĂ€gen pro BehĂ€lter effizient sind, sind nicht erforderlich oder wĂŒnschenswert. Wenn diese FĂ€lle hĂ€ufig auftreten, funktioniert das Hashing nicht gut, und dies muss behoben werden.
Hashing mit offener Adressierung
Dieses Verfahren wird abgekĂŒrzt auch offenes Hashing oder geschlossenes Hashing genannt. Der Name offenes Hashing bezieht sich auf die offene Adressierung, wĂ€hrend der Name geschlossenes Hashing sich auf die begrenzte Anzahl möglicher SchlĂŒssel im BehĂ€lter bezieht.
Beim Hashing mit offener Adressierung kann jedem BehĂ€lter nur eine feste Anzahl von SchlĂŒsseln zugewiesen werden. HĂ€ufig wĂ€hlt man einfach einen einzigen möglichen SchlĂŒssel pro BehĂ€lter. Im Kollisionsfall muss dann nach einem alternativen BehĂ€lter gesucht werden. Dabei geht man so vor, dass man fĂŒr m BehĂ€lter eine ganze Folge von m Hash-Funktionen definiert. FĂŒhrt die Anwendung der ersten Hash-Funktion, nennen wir sie h1, zu einer Kollision, so wendet man h2 an. FĂŒhrt diese ebenfalls zu einer Kollision (d. h. der entsprechende BehĂ€lter ist bereits belegt), so wendet man h3 an, und so weiter, bis hm bzw. bis ein leerer BehĂ€lter gefunden wird. Die Bezeichnung âoffene Adressierungâ ergibt sich aus der Eigenschaft, dass durch Kollisionen gleiche SchlĂŒssel unterschiedliche Adressen zugewiesen bekommen können.
Alle EintragsdatensĂ€tze werden im BehĂ€lter selbst gespeichert. Wenn ein neuer Eintrag eingefĂŒgt werden muss, werden die BehĂ€lter untersucht, beginnend mit dem Hash-zu-Slot und fortschreitend in einer bestimmten PrĂŒfsequenz, bis ein unbesetzter BehĂ€lter gefunden wird. Bei der Suche nach einem Eintrag werden die BehĂ€lter in der gleichen Reihenfolge durchsucht, bis entweder der Zieldatensatz oder ein ungenutzter BehĂ€lter gefunden wird, was anzeigt, dass es keinen solchen SchlĂŒssel in der Tabelle gibt. Der Name offenes Hashing bezieht sich darauf, dass der Ort des Elements nicht durch seinen Hashwert bestimmt wird.
Kuckucks-Hashing
Kuckucks-Hashing ist ein weiteres Verfahren, Kollisionen in einer Tabelle zu vermeiden. Der Name leitet sich vom Verhalten des Kuckucks ab, Eier aus einem Nest zu entfernen, um ein eigenes Ei hineinzulegen.
Das Prinzip ist, zwei Hash-Funktionen einzusetzen. Das ergibt zwei mögliche Speicherorte in einer Hashtabelle, was sehr viel hĂ€ufiger eine konstante Zugriffszeit garantiert. Ein neuer SchlĂŒssel wird an einem der zwei möglichen Orte gespeichert. Sollte die erste Zielposition besetzt sein, wird der bereits vorhandene SchlĂŒssel auf seine alternative Position versetzt und an seiner Stelle der neue SchlĂŒssel gespeichert. Sollte die alternative Position besetzt sein, so wird wiederum der SchlĂŒssel auf dieser Position auf seine alternative Position transferiert, und so fort. Wenn diese Prozedur zu einer unendlichen Schleife fĂŒhrt (ĂŒblicherweise bricht man nach log ⥠⥠n {\displaystyle \log n} Schritten ab), wird die Hashtabelle mit zwei neuen Hash-Funktionen neu aufgebaut. Die Wahrscheinlichkeit fĂŒr ein solches Rehashing liegt in der GröĂenordnung von O ( 1 / n ) {\displaystyle O(1/n)} fĂŒr jedes EinfĂŒgen.
Algorithmen
Lineares Sondieren
Die einfachste Möglichkeit zur Definition einer solchen Folge besteht darin, so lange den jeweils nĂ€chsten BehĂ€lter zu prĂŒfen, bis man auf einen leeren BehĂ€lter trifft. Die Definition der Folge von Hashfunktionen sieht dann so aus:
h i ( x ) = ( h ( x ) + i ) mod m {\displaystyle h_{i}(x)=(h(x)+i)\;{\bmod {~}}m}
Die Anwendung des Operators Modulo hat mit der begrenzten Zahl von BehĂ€ltern zu tun: Wurde der letzte BehĂ€lter geprĂŒft, so beginnt man wieder beim ersten BehĂ€lter. Das Problem dieser Methode ist, dass sich so schnell Ketten oder Cluster bilden und die Zugriffszeiten im Bereich solcher Ketten schnell ansteigen. Das lineare Sondieren ist daher wenig effizient. Sein Vorteil ist jedoch, dass â im Gegensatz zu anderen Sondierungsverfahren â alle BehĂ€lter der Tabelle benutzt werden.
Um ein Element x {\displaystyle x} zu finden, berechnet man h ( x ) {\displaystyle h(x)} und beginnt dort mit der Suche. Man durchlĂ€uft das Array, bis entweder das Element gefunden oder ein leerer BehĂ€lter erkannt wird. Löschungen sind etwas schwieriger als beim Hashing mit Verkettung, denn man kann nicht einfach eine Suche durchfĂŒhren und das Element dort löschen, wo man es findet. Löschungen werden hĂ€ufig mithilfe von sogenannten Tombstones umgesetzt. Beim Löschen eines Elements wird markiert, dass der BehĂ€lter leer ist und zuvor belegt war. Beim Suchen bleibt man nicht bei einem Tombstone stehen. Stattdessen setzt man die Suche fort. Man muss dabei auf das Ende des Arrays achten und gegebenenfalls wieder am Anfang beginnen. Beim EinfĂŒgen kann jeder Tombstone, auf den man stöĂt durch einen leeren BehĂ€lter ersetzt werden.
In der Praxis ist lineares Sondieren eine der schnellsten Hashing-Algorithmen. Vorteilhaft ist der geringe Speicheraufwand. Man benötigt lediglich ein Array und eine sehr einfache Hashfunktion. Hervorragende LokalitĂ€t: Bei Kollisionen suchen wir nur an benachbarten Orten im Array. Bei der linearen Abtastung kommt es zu erheblichen LeistungseinbuĂen, wenn der Lastfaktor hoch wird. Die Anzahl der Kollisionen nimmt tendenziell mit der Anzahl der bestehenden Kollisionen zu. Dies wird als primĂ€res Clustering bezeichnet.cite-ref-1[1]
Wenn n {\displaystyle n} Elemente in eine Array mit m {\displaystyle m} BehĂ€ltern eingefĂŒgt werden, ist der Lastfaktor α α = n m {\displaystyle \alpha ={\tfrac {n}{m}}} . Der Erwartungswert fĂŒr die Anzahl der SchlĂŒssel pro BehĂ€lter bei einer nicht erfolgreichen Suche betrĂ€gt dann 1 2 â
â
( 1 + ( 1 1 â â α α ) 2 ) {\displaystyle {\tfrac {1}{2}}\cdot \left(1+\left({\tfrac {1}{1-\alpha }}\right)^{2}\right)} . Der Erwartungswert bei einer erfolgreichen Suche eines zufĂ€lligen Elements betrĂ€gt 1 2 â
â
( 1 + 1 1 â â α α ) {\displaystyle {\tfrac {1}{2}}\cdot \left(1+{\tfrac {1}{1-\alpha }}\right)} .cite-ref-2[2]cite-ref-0-3-0[3]
Quadratisches Sondieren
Wie beim linearen Sondieren wird nach einem neuen freien Speicher gesucht, allerdings nicht sequenziell, sondern mit stetig quadratisch wachsendem Abstand zur ursprĂŒnglichen Position und in beide Richtungen. Verursacht h ( k ) {\displaystyle h(k)} eine Kollision, so werden nacheinander h ( k ) + 1 , h ( k ) â â 1 , h ( k ) + 4 , h ( k ) â â 4 , h ( k ) + 9 {\displaystyle h(k)+1,h(k)-1,h(k)+4,h(k)-4,h(k)+9} usw. probiert. In Formeln ausgedrĂŒckt: h i ( x ) = ( h ( x ) + ( â â 1 ) i + 1 â
â
â i 2 â 2 ) mod m {\displaystyle h_{i}(x)=\left(h(x)+(-1)^{i+1}\cdot \left\lceil {\frac {i}{2}}\right\rceil ^{2}\right){\bmod {~}}m}
Den stĂ€ndigen Wechsel des Vorzeichens bei dieser Kollisionsstrategie nennt man auch âalternierendes quadratisches Sondierenâ oder âquadratisches Sondieren mit Verfeinerungâ. WĂ€hlt man die Anzahl der BehĂ€lter geschickt (nĂ€mlich m = 4 â
â
j + 3 {\displaystyle m=4\cdot j+3} , m {\displaystyle m} ist Primzahl), so erzeugt jede Sondierungsfolge h 0 ( x ) {\displaystyle h_{0}(x)} bis h m â â 1 ( x ) {\displaystyle h_{m-1}(x)} eine Permutation der Zahlen 0 bis m â â 1 {\displaystyle m-1} ; so wird also sichergestellt, dass jeder BehĂ€lter getroffen wird.cite-ref-4[4]
Quadratisches Sondieren ergibt keine Verbesserung fĂŒr die Wahrscheinlichkeit eine Sondierung durchfĂŒhren zu mĂŒssen ( h 0 ( x ) = h 0 ( y ) {\displaystyle h_{0}(x)=h_{0}(y)} ), kann aber die Wahrscheinlichkeit von Kollisionen wĂ€hrend der Sondierung ( h 0 ( x ) = h k ( y ) {\displaystyle h_{0}(x)=h_{k}(y)} ) herabsetzen, d. h. Clusterbildung wird vermieden.
Das quadratische Sondieren verhindert die Cluster, die beim linearen Sondieren entstehen, ist aber nicht optimal. Beim quadratische Sondieren entsteht sogenanntes sekundĂ€res Clustering. Dies geschieht, denn wenn zwei SchlĂŒssel nahe beieinander liegende Hashwerte haben, liegen die entsprechenden sondierten BehĂ€lter ebenfalls nahe beieinander. ZusĂ€tzlich gibt es einen anderen Nachteil. Im Gegensatz zum linearen Sondieren, das garantiert jeden BehĂ€lter ausprobiert, stehen fĂŒr das quadratische Sondieren in einigen FĂ€llen nur â â m 2 â â {\displaystyle \lfloor {\tfrac {m}{2}}\rfloor } BehĂ€lter zur Auswahl.cite-ref-5[5]
Der Erwartungswert fĂŒr die Anzahl der SchlĂŒssel pro BehĂ€lter bei einer nicht erfolgreichen Suche betrĂ€gt 1 1 â â α α â â α α + ln ⥠⥠( 1 1 â â α α ) {\displaystyle {\tfrac {1}{1-\alpha }}-\alpha +\ln \left({\tfrac {1}{1-\alpha }}\right)} . Der Erwartungswert bei einer erfolgreichen Suche eines zufĂ€lligen Elements betrĂ€gt 1 + ln ⥠⥠( 1 1 â â α α ) â â α α 2 {\displaystyle 1+\ln \left({\tfrac {1}{1-\alpha }}\right)-{\tfrac {\alpha }{2}}} .cite-ref-0-3-1[3]
Doppel-Hashing
Beim Doppel-Hashing werden zwei unabhĂ€ngige Hash-Funktionen h {\displaystyle h} und h âČ {\displaystyle h'} angewandt. Diese heiĂen unabhĂ€ngig, wenn die Wahrscheinlichkeit fĂŒr eine sogenannte Doppelkollision, d. h. h ( x ) = h ( y ) â§ â§ h âČ ( x ) = h âČ ( y ) {\displaystyle h(x)=h(y)\land h'(x)=h'(y)} gleich 1 / m 2 {\displaystyle 1/m^{2}} und damit minimal ist. Die Folge von Hash-Funktionen, die nun mittels h {\displaystyle h} und h âČ {\displaystyle h'} gebildet werden, sieht so aus:
h i ( x ) = ( h ( x ) + h âČ ( x ) â
â
i ) mod m {\displaystyle h_{i}(x)=(h(x)+h'(x)\cdot i)~{\bmod {~}}m}
Die Kosten fĂŒr diese Methode sind nahe den Kosten fĂŒr ein ideales Hashing.
Eine interessante Variante des Doppel-Hashing ist das Robin-Hood-Hashing. Die Idee ist, dass ein neuer SchlĂŒssel einen bereits eingefĂŒgten SchlĂŒssel verdrĂ€ngen kann, wenn seine PrĂŒfwert gröĂer ist als die des SchlĂŒssels an der aktuellen Position. Der Nettoeffekt davon ist, dass es die Worst Case Suchzeiten in der Tabelle reduziert. Weil sowohl der Worst Case als auch die Variation der Anzahl der Sonden drastisch reduziert werden, besteht eine interessante Variation darin, die Tabelle beginnend mit der erwarteten erfolgreichen PrĂŒfwert zu durchsuchen und dann von dieser Position aus in beide Richtungen zu erweitern.
Der Erwartungswert fĂŒr die Anzahl der SchlĂŒssel pro BehĂ€lter bei einer nicht erfolgreichen Suche betrĂ€gt 1 1 â â α α {\displaystyle {\tfrac {1}{1-\alpha }}} . Der Erwartungswert bei einer erfolgreichen Suche eines zufĂ€lligen Elements betrĂ€gt 1 α α â
â
ln ⥠⥠( 1 1 â â α α ) {\displaystyle {\tfrac {1}{\alpha }}\cdot \ln \left({\tfrac {1}{1-\alpha }}\right)} .cite-ref-0-3-2[3]
Brent-Bansley-Hashing
Beim Brent-Bansley-Hashing wird geprĂŒft, ob der Platz, an dem das Element eingefĂŒgt werden soll, frei ist. Ist das der Fall, dann wird das Element dort eingefĂŒgt. Ist der Platz jedoch belegt, dann wird anhand des gerade berechneten Platzes jeweils fĂŒr das einzufĂŒgende Element und fĂŒr das Element, das schon an dem Platz ist, ein neuer Platz in der Tabelle berechnet. Sind die beiden neu berechneten PlĂ€tze auch belegt, wiederholt sich die Prozedur fĂŒr den neu berechneten belegten Platz des einzufĂŒgenden Elementes. Wird jedoch fĂŒr das einzufĂŒgende Element ein Platz berechnet, der frei ist, wird das Element dort eingefĂŒgt. Ist der Platz jedoch belegt und der berechnete Platz frei fĂŒr das Element, das im vorherigen Durchlauf den Platz fĂŒr das einzufĂŒgende Element belegt hat, dann werden die beiden PlĂ€tze der Elemente vertauscht und damit konnte das einzufĂŒgende Element in der Tabelle untergebracht werden.
Dynamisches Hashing
Bei steigendem FĂŒllgrad der Tabelle steigt die Wahrscheinlichkeit von Kollisionen deutlich an. SpĂ€testens wenn die Anzahl der indizierten DatensĂ€tze gröĂer ist, als die KapazitĂ€t der Tabelle, werden Kollisionen unvermeidbar. Das bedeutet, dass das Verfahren einen zunehmenden Aufwand zur Kollisionslösung aufwenden muss. Um dies zu vermeiden, wird beim Dynamischen Hashing die Hashtabelle bei Bedarf vergröĂert. Dies hat jedoch zwangslĂ€ufig Auswirkungen auf den Wertebereich der Hash-Funktion, der nun ebenfalls erweitert werden muss. Eine Ănderung der Hash-Funktion wiederum hat jedoch den nachteiligen Effekt, dass sich ebenfalls die Hash-Werte fĂŒr bereits gespeicherte Daten Ă€ndern. FĂŒr das dynamische Hashing wurde dafĂŒr eigens eine Klasse von Hash-Funktionen entwickelt, deren Wertebereich vergröĂert werden kann, ohne die bereits gespeicherten Hash-Werte zu verĂ€ndern.
Vorteile
âą Es gibt keine obere Grenze fĂŒr das Datenvolumen
⹠EintrÀge können ohne Probleme gelöscht werden
âą Adresskollisionen fĂŒhren nicht zur Clusterbildung.
Nachteile
Falls nicht eine ordnungserhaltende Hashfunktion zum Einsatz kam:
⹠kein effizientes Durchlaufen der EintrÀge nach einer Ordnung
âą keine effiziente Suche nach dem Eintrag mit dem kleinsten oder gröĂten SchlĂŒssel
Anwendung
Hashtabellen werden in praktisch jeder modernen Applikation eingesetzt, etwa zur Implementierung von Mengen (Sets) oder Caches.
Assoziative Arrays
Ein typischer Anwendungsfall sind daneben assoziative Arrays (auch bekannt als Map, Lookup Table, Dictionary oder Wörterbuch); das Nachschlagen der mit einem SchlĂŒssel assoziierten Daten kann mittels einer Hashtabelle schnell und elegant implementiert werden.
Symboltabellen
Symboltabellen, wie sie Compiler oder Interpreter verwenden, werden meist ebenfalls als Hashtabelle realisiert.
Datenbanken
Wichtig sind Hashtabellen auch fĂŒr Datenbanken zur Indizierung von Tabellen. Ein Hashindex kann unter gĂŒnstigen Bedingungen zu idealen Zugriffszeiten fĂŒhren.
Hashtabellen ermöglichen eine sehr schnelle Suche in groĂen Datenmengen, da mit der Berechnung des Hashwertes in einem einzigen Schritt die Anzahl der möglichen Zielobjekte eingeschrĂ€nkt wird. Damit gehören Hashtabellen zu den effizientesten Indexstrukturen.
Ein groĂer Nachteil ist jedoch die Gefahr der Entartung durch Kollisionen, die bei einem stetigen Wachstum der Datenmenge unausweichlich sind (wenn die Tabelle nicht vergröĂert und jedes darin enthaltene Element neu gehasht wird). Daher, wegen ungĂŒnstiger IO-Zugriffsmuster, wenn die Hashtabelle auf einem DatentrĂ€ger gespeichert ist und der fehlenden Möglichkeit Intervalle gemÀà einer Ordnungsrelation effizient zu iterieren, muss der Einsatz von Datenbanksystemen gegenĂŒber alternativen Indexdatenstrukturen, wie z. B. B+-BĂ€umen, abgewogen werden.
Die meisten Hashfunktionen erlauben nicht die Bewegung zum nĂ€chsten oder vorherigen Datensatz gemÀà einer Ordnungsrelation, da sie gezielt die Daten âmischenâ, um sie gleichmĂ€Ăig im Werteraum zu verteilen. Nur spezielle âordnungserhaltendeâ Hashfunktionen erlauben eine derartige Iteration gemÀà ihrer Ordnungsrelation und damit die Abfrage mit UngleichheitsverknĂŒpfungen (âgröĂer alsâ, âkleiner alsâ) oder den sortierten Zugriff auf alle Werte.cite-ref-6[6] Um solche Verfahren effizient einsetzen zu können, ist meist eine vorherige Analyse der Datenverteilung notwendig. Sie werden daher meist nur in Datenbanksystemen angewendet, die eine solche Analyse auch beispielsweise zur Anfrageoptimierung durchfĂŒhren.
Siehe auch
Literatur
âą MAURER, Ward Douglas: Hash Table Methods. In: ACM Computing Surveys (CSUR). Band 7, Nr. 1, MĂ€rz 1975, S. 5â19, doi:10.1145/356643.356645 (englisch).
âą LITWIN, Witold: Linear Hashing: a new tool for file and table addressing. In: IEEE Int. Conf. Very Large Database VLDB'80. Band 6, Oktober 1980, S. 212â223 (englisch, northwestern.edu [PDF; abgerufen am 18. MĂ€rz 2021]).
âą CELIS, Pedro; LARSON, Per-Ake; MUNRO, J. Ian: Robin hood hashing. In: 26th Annual Symposium on Foundations of Computer Science (SFCS'1985). 1985, S. 281â288, doi:10.1109/SFCS.1985.48 (englisch).
âą PAGH, Rasmus; RODLER, Flemming Friche: Cuckoo hashing. In: Journal of Algorithms. Band 51, Nr. 2, 2004, S. 122â144, doi:10.1016/j.jalgor.2003.12.002 (englisch).
âą BURKHARD, Walter A.: Double hashing with passbits. In: Information processing letters. Band 96, Nr. 5, 16. Dezember 2005, S. 162â166, doi:10.1016/j.ipl.2005.08.005 (englisch).
âą Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest: Introduction to Algorithms. 1184 Seiten; The MIT Press 2001, ISBN 0-262-53196-8.
âą Alfons Kemper, AndrĂ© Eickler: Datenbanksysteme â Eine EinfĂŒhrung. 7. Auflage, Oldenbourg Verlag 2009, ISBN 3-486-57690-9. Seite 220f
âą Christian Ullenboom: Java ist auch eine Insel, 1444 Seiten; Galileo Press 2007, ISBN 3-89842-838-9; online als Openbook verfĂŒgbar.
Weblinks
⹠Umfangreiche Hashbibliothek mit weitreichender AnalysefunktionalitÀt
âą Cuckoo Hashing (englisch, PDF, 169 KiB)
âą Hash Table
Einzelnachweise
cite-note-11. â Stanford University: Linear Probing
cite-note-22. â Uri Zwick, Tel Aviv University: Hash Tables: Linear Probing
cite-note-0-33. â O. Bittel, Hochschule Konstanz: Algorithmen und Datenstrukturen â Hashverfahren
cite-note-44. â Martin Lercher, Heinrich-Heine-UniversitĂ€t DĂŒsseldorf: Algorithmen und Datenstrukturen - Hash-Verfahren
cite-note-55. â Dave Mount, University of Maryland: Hashing
cite-note-66. â Davi de Castro Reis, Djamel Belazzougui, Fabiano Cupertino Botelho: Minimal Perfect Hash Functions - Introduction. SourceForge, abgerufen am 8. November 2010 (englisch).